package scan

import (
	"database/sql"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"time"
)

type basicScanner struct {
	dest reflect.Value
}

func (bs *basicScanner) Scan(srcIfc interface{}) error {
	if srcIfc == nil {
		bs.dest.Set(reflect.Zero(bs.dest.Type()))
		return nil
	}

	if scanner := getScanner(bs.dest.Addr()); scanner != nil {
		return scanner.Scan(srcIfc)
	}

	//fmt.Printf("srcIfc %+#v\n", srcIfc)
	switch src := srcIfc.(type) {
	case int64:
	case float64:
	case []uint8:
		return scanBytes(src,getRealDest(bs.dest))
	case string:
	case bool:
	default:
		return fmt.Errorf("bsql basicScanner unexpected src: %T(%v)", src, src)
	}
	return nil
}

func scanBytes(src []uint8, dest reflect.Value) error {
	switch dest.Kind() {
	case reflect.Int64, reflect.Int, reflect.Int32,reflect.Int16,reflect.Int8:
		if data,err := strconv.ParseInt(string(src),10,64);err == nil {
			dest.SetInt(data)
		}else {
			return errorCannotAssign(src,dest)
		}
	case reflect.Uint64, reflect.Uint, reflect.Uint32,reflect.Uint16,reflect.Uint8:
		if data,err := strconv.ParseUint(string(src),10,64);err == nil {
			dest.SetUint(data)
		}else {
			return errorCannotAssign(src,dest)
		}
	case reflect.String:
		dest.SetString(string(src))
	case reflect.Bool:
		dest.SetBool(src[0] == '1')
	case reflect.Struct:
		switch dest.Addr().Interface().(type) {
		case *time.Time:
			d, err := time.ParseInLocation("2006-01-02 15:04:05", string(src),time.Local)
			if err != nil {
				return errors.New("bsql: parse time error")
			}
			dest.Set(reflect.ValueOf(d))
		default:
			return errorCannotAssign(src,dest)
		}
	default:
		return errors.New("bsql: can not support type")
	}
	return nil
}

// the preceding steps ensured that dest is valid
func getRealDest(dest reflect.Value) reflect.Value {
	if dest.Kind() == reflect.Interface && !dest.IsNil() {
		dest = dest.Elem()
	}
	for dest.Kind() == reflect.Ptr {
		if dest.IsNil() {
			dest.Set(reflect.New(dest.Type().Elem()))
		}
		dest = dest.Elem()
	}
	return dest
}

func getScanner(addr reflect.Value) sql.Scanner {
	for ; addr.Kind() == reflect.Ptr; addr = addr.Elem() {
		if addr.IsNil() {
			addr.Set(reflect.New(addr.Type().Elem()))
		}
		if scanner := trySqlScanner(addr.Interface()); scanner != nil {
			return scanner
		}
	}
	return nil
}

func errorCannotAssign(src interface{}, dest reflect.Value) error {
	return fmt.Errorf("bsql: cannot assign %T(%v) to %v", src, src, dest.Type())
}